<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Canvas Text Rendering Character by Character</title>
    <style>
        canvas {
            border: 1px solid #000;
            z-index: 50; /* 确保这个值足够高，以覆盖其他内容 */
            position: absolute; /* 避免占用页面空间 */
        }

         /* 隐藏输入框，但保持其可接受输入 */
         #editableDiv {
        opacity: 0; /* 使div不可见但仍然可以接收输入 */
        position: absolute; /* 避免占用页面空间 */
        top: -100;
        left: -1000;
        width: 110px; /* 最小化可编辑区域的尺寸 */
        height: 10px; /* 最小化可编辑区域的尺寸 */
        overflow: hidden; /* 避免滚动条出现 */
    }

    #contextMenu {
            display: none;
            position: absolute;
            z-index: 1000; /* 确保这个值足够高，以覆盖其他内容 */
            background-color: white;
            border: 1px solid #ddd;
            padding: 10px;
            box-shadow: 3px 3px 5px rgba(0,0,0,0.2); /* 阴影效果 */
        }

        #contextMenu ul {
            list-style: none;
            margin: 0;
            padding: 0;
        }

        #contextMenu ul li {
            padding: 8px;
            cursor: pointer;
        }

        #contextMenu ul li:hover {
            background-color: #f7f7f7;
        }

        .button-container {
            text-align: center; /* 将按钮居中对齐 */
            margin-top: 10px; /* 在容器和画布之间添加一些空间 */
            position: absolute;
            z-index: 150; /* 确保这个值足够高，以覆盖其他内容 */
        }

    </style>



</head>
<body>
    <canvas id="canvas" width="800" height="300"></canvas>
     <!-- 看不见的输入框 -->
 
     <div id="editableDiv" contenteditable="true"></div>

     <div id="contextMenu">
        <ul>
            <li id="copy">复制</li>
            <li id="paste">粘贴</li>
        </ul>
    </div>


    <div class="button-container">
        <button id="fontSizeBtn">字体大小28</button><button id="fontSizeBtn2">字体大小50</button>
        <button id="fontColorBtn">字体颜色(红)</button>
        <button id="fontColorBtn2">字体颜色(蓝)</button>
    </div>


    <script>
           var textData = {
            text: "您好中国！广州力麒智能科技有限公司是高新技术企业、广东省专精特新企业。主营业务涵盖标杆大厅建设咨询输出、国产信创政务产业、软件开发咨询、政务服务能力提升培训及人力服务外包等",
            styles: [
                { start: 0, end: 2, font: "黑体", fontSize:40,color: "red" },
                { start: 2, end: 6, font: "楷体", fontSize:80,color: "green" }
            ],
            height: 80 // 假设的行高，实际中应根据最大字体计算
        };

       document.addEventListener("DOMContentLoaded", function() {
       


        const canvas = document.getElementById('canvas');
        const ctx = canvas.getContext('2d');
     
        let isSelecting = false;
        let selectionStart = null;
        let selectionEnd = null;
        const charWidths = [];
        let lineHeight = 0;        // 添加行高变量
        let cursorPosition = null; // 光标位置         
        let cursorVisible = true; // 控制光标显示的状态
        let hasSelection = false;  // 添加一个变量来跟踪选择状态
        let isComposing = false;
        let linesData = []; // 用于存储处理后的行数据


  

        
                


       
        const editableDiv = document.getElementById('editableDiv');
        let initialText = "";      // 记录开始输入时的文本
        let initialSelectionStart = 0; // 记录开始输入时的光标或选区开始位置
        let initialSelectionEnd = 0;   // 记录开始输入时的光标或选区结束位置
        let initialTextLength = 0;
		let initialCursorPosition = 0;
        
        editableDiv.style.width = '1px';
        editableDiv.style.height = '1px';
        editableDiv.style.opacity = 0;


        const contextMenu = document.getElementById('contextMenu');
        const copyMenuItem = document.getElementById('copy');
        const pasteMenuItem = document.getElementById('paste');

        const fontSizeBtn = document.getElementById('fontSizeBtn');
        const fontSizeBtn2 = document.getElementById('fontSizeBtn2');
        const fontColorBtn = document.getElementById('fontColorBtn');
        const fontColorBtn2 = document.getElementById('fontColorBtn2');

        fontSizeBtn.addEventListener('click', function() {
            console.log('C3 click TextSizeSet 28');
            updateSelectedTextSize(28);
            compressStyle();
            generateLinesData();
           
            renderText();
        });

        fontSizeBtn2.addEventListener('click', function() {
            console.log('C3 click TextSizeSet 50');
            updateSelectedTextSize(50);
            compressStyle();
              generateLinesData();
           
            renderText();
        });

        fontColorBtn.addEventListener('click', function() {
            updateSelectedTextColor('red');
            compressStyle();
            renderText();
        });

        fontColorBtn2.addEventListener('click', function() {
            updateSelectedTextColor('blue');
            compressStyle();
            renderText();
        });

        function updateSelectedTextSize(newFontSize) {
        // 分割或合并styles对象，以确保选区内的每一部分都可以正确更新
            let newStyles = [];
            let updated = false;

            textData.styles.forEach(style => {
                if (style.end <= selectionStart || style.start >= selectionEnd) {
                    // 完全不在选区内的样式直接保留
                    newStyles.push(style);
                } else {
                    // 部分或完全在选区内的样式需要细分和更新
                    if (style.start < selectionStart) {
                        // 选区前的样式分割出来
                        newStyles.push({start: style.start, end: selectionStart, fontSize: style.fontSize, font: style.font, color: style.color});
                    }

                    // 更新选区内的样式
                    newStyles.push({start: Math.max(style.start, selectionStart), end: Math.min(style.end, selectionEnd), fontSize: newFontSize, font: style.font, color: style.color});
                    updated = true;

                    if (style.end > selectionEnd) {
                        // 选区后的样式分割出来
                        newStyles.push({start: selectionEnd, end: style.end, fontSize: style.fontSize, font: style.font, color: style.color});
                    }
                }
            });

            // 如果选区内原本没有样式，则创建一个新的样式对象
            if (!updated) {
                newStyles.push({start: selectionStart, end: selectionEnd, fontSize: newFontSize, font: "黑体", color: "black"}); // 默认字体属性可能需要调整
            }

            // 用新的样式数组替换旧的
            textData.styles = newStyles;

            // 根据textData重新计算linesData中的样式，这一步骤取决于你的linesData构建方式
            // 通常，你需要根据textData中的全局样式来更新每一行的局部样式
            updateLinesDataStyles();

            

            // 重新渲染文本以应用字体大小的更新
            renderText();
        }

        function updateLinesDataStyles() {
            // 这个函数需要根据你的具体实现来编写
            // 基本思路是遍历linesData的每一行，根据textData的styles来更新行内的styles
            generateLinesData();
           // 重新生成linesData，包括文本、样式和行高
    		

            // 接下来，你可能需要根据新的linesData来更新文档的其他相关属性，
            // 比如整体高度等，这取决于你的具体实现。
            updateTextLayout(); // 假设这个函数处理文档布局的其他更新
        }

        function updateTextLayout() {
            let currentY = 0; // 假设文档的起始Y坐标为0
            linesData.forEach(line => {
                // 假设line对象有一个y属性，表示该行的起始Y坐标
                line.y = currentY;
                currentY += line.height; // 更新下一行的起始Y坐标
            });

            // 更新整个文档的高度
            textData.totalHeight = currentY;
        }

        function updateSelectedTextColor(newColor) {
        let globalPos = 0; // 全局位置指针，用于追踪选区在整个文本中的位置

            linesData.forEach(line => {
                const lineLength = line.text.length;
                const localSelectionStart = Math.max(selectionStart - globalPos, 0); // 当前行的本地选区开始位置
                const localSelectionEnd = Math.min(selectionEnd - globalPos, lineLength); // 当前行的本地选区结束位置

                if (localSelectionStart < localSelectionEnd) { // 检查选区是否在当前行内
                    let newStyles = [];

                    line.styles.forEach(style => {
                        if (style.end <= localSelectionStart || style.start >= localSelectionEnd) {
                            // 样式与选区无重叠
                            newStyles.push(style);
                        } else {
                            // 样式与选区有重叠
                            if (style.start < localSelectionStart) {
                                newStyles.push({
                                    ...style,
                                    end: localSelectionStart,
                                });
                            }

                            if (style.end > localSelectionEnd) {
                                newStyles.push({
                                    ...style,
                                    start: localSelectionEnd,
                                });
                            }

                            newStyles.push({
                                start: Math.max(style.start, localSelectionStart),
                                end: Math.min(style.end, localSelectionEnd),
                                font: style.font,
                                fontSize: style.fontSize,
                                color: newColor // 应用新颜色
                            });
                        }
                    });

                    // 避免重叠样式
                    newStyles = mergeStyles(newStyles);
                    line.styles = newStyles; // 更新当前行的样式
                }

                globalPos += lineLength; // 更新全局位置指针
            });
        }

        function mergeStyles(styles) {
            // 此函数用于合并同一行中相邻且样式相同的样式块
            // 注意：这里的实现取决于具体需求，可能需要根据实际情况调整
            return styles.sort((a, b) => a.start - b.start); // 简单示例：按开始位置排序
        }

        function findLineIndexForPosition(position) {
            // 返回给定位置的行索引
            for (let i = 0; i < linesData.length; i++) {
                if (linesData[i].start <= position && position <= linesData[i].end) {
                    return i;
                }
            }
            return -1; // 如果位置不在任何行中，则返回-1
        }


        // function updateSelectedTextColor(newColor) {
        //    // 新的样式数组
        //         let newStyles = [];

        //     // 遍历现有样式
        //     textData.styles.forEach(style => {
        //         if (style.end <= selectionStart || style.start >= selectionEnd) {
        //             // 当前样式与选区无重叠，保留此样式
        //             newStyles.push(style);
        //         } else {
        //             // 样式与选区有部分重叠
        //             if (style.start < selectionStart) {
        //                 // 样式开始于选区之前，分割此样式
        //                 newStyles.push({
        //                     start: style.start,
        //                     end: selectionStart,
        //                     font: style.font,
        //                     fontSize: style.fontSize,
        //                     color: style.color
        //                 });
        //             }

        //             // 更新选区内的样式颜色
        //             newStyles.push({
        //                 start: Math.max(style.start, selectionStart),
        //                 end: Math.min(style.end, selectionEnd),
        //                 font: style.font,
        //                 fontSize: style.fontSize,
        //                 color: newColor
        //             });

        //             if (style.end > selectionEnd) {
        //                 // 样式延续到选区之后，分割此样式
        //                 newStyles.push({
        //                     start: selectionEnd,
        //                     end: style.end,
        //                     font: style.font,
        //                     fontSize: style.fontSize,
        //                     color: style.color
        //                 });
        //             }
        //         }
        //     });

        //     // 处理选区中新文本的样式，若完全不重叠
        //     if (!newStyles.some(style => style.start <= selectionStart && style.end >= selectionEnd)) {
        //         newStyles.push({
        //             start: selectionStart,
        //             end: selectionEnd,
        //             font: "Arial", // 使用默认字体
        //             fontSize: 16, // 使用默认字体大小
        //             color: newColor // 新颜色
        //         });
        //     }

        //     // 更新textData.styles为新的样式数组
        //     textData.styles = newStyles;
        // }

        function compressStyle() {
            if (textData.styles.length === 0) return;

            // 首先，根据样式段的开始位置进行排序，确保样式按顺序处理
            textData.styles.sort((a, b) => a.start - b.start);

            let compressedStyles = [textData.styles[0]];

            for (let i = 1; i < textData.styles.length; i++) {
                let lastStyle = compressedStyles[compressedStyles.length - 1];
                let currentStyle = textData.styles[i];

                // 检查当前样式是否与上一个样式相同（字体、字号、颜色）
                if (lastStyle.font === currentStyle.font &&
                    lastStyle.fontSize === currentStyle.fontSize &&
                    lastStyle.color === currentStyle.color &&
                    lastStyle.end === currentStyle.start) {
                    // 如果当前样式与上一个样式完全相同，则合并这两个样式段
                    lastStyle.end = currentStyle.end;
                } else {
                    compressedStyles.push(currentStyle);
                }
            }

            // 更新textData的styles为压缩后的样式数组
            textData.styles = compressedStyles;

           
        }


        canvas.addEventListener('contextmenu', (e) => {
            e.preventDefault(); // 阻止默认的右键菜单

            const rect = canvas.getBoundingClientRect(); // 获取canvas的边界
            const menuWidth = contextMenu.offsetWidth;
            const menuHeight = contextMenu.offsetHeight;

            // 计算菜单的最佳位置，确保它不会超出canvas边界
            let x = e.clientX - rect.left; // 转换为相对于canvas的位置
            let y = e.clientY - rect.top;

            console.log(x, y);
            // 如果菜单超出右侧
            if (x + menuWidth > canvas.width) {
                x = canvas.width - menuWidth;
            }
            // 如果菜单超出底部
            if (y + menuHeight > canvas.height) {
                y = canvas.height - menuHeight;
            }

            // 设置自定义菜单的位置并显示
            contextMenu.style.top = `{y}px`;
            contextMenu.style.left = `{x}px`;
            contextMenu.style.display = 'block';
        });


        // 点击复制
        copyMenuItem.addEventListener('click', () => {
            copyToClipboard();  // 前面定义的复制函数
            contextMenu.style.display = 'none';  // 隐藏菜单
        });

        // 点击粘贴
        pasteMenuItem.addEventListener('click', () => {
            // 粘贴内容到画布并更新textData.text
            navigator.clipboard.readText().then((text) => {
                // 假设我们粘贴到光标当前位置
                insertTextAtCursor(text);
                renderText();  // 重新渲染画布
            });
            contextMenu.style.display = 'none';  // 隐藏菜单
        });

        // 点击其他地方时隐藏菜单
        document.addEventListener('click', (e) => {
            if (e.target !== contextMenu) {
                contextMenu.style.display = 'none';
            }
        });
   

    function insertTextAtCursor(text) {
        // 粘贴时的文本插入逻辑
        const beforeCursor = textData.text.slice(0, cursorPosition);
        const afterCursor = textData.text.slice(cursorPosition);
        textData.text = beforeCursor + text + afterCursor;
        cursorPosition += text.length;  // 更新光标位置
        // 需要更新样式信息（省略）
    }



     	 canvas.addEventListener('click', function() {
        editableDiv.focus(); // 当画布被点击时，设置焦点到editableDiv
    	});

        // 根据需要添加更多事件监听器，如compositionstart, compositionupdate

        // 开始输入复合字符（如中文字符）
        editableDiv.addEventListener('compositionstart', function(e) {
            isComposing = true;
            console.log('开始输入  =================================');
            initialText = textData.text;
            initialSelectionStart = selectionStart; // 假设你已经有这些值
            initialSelectionEnd = selectionEnd;
                        
        
        });

        // 输入复合字符过程中（可选，如果你需要实时获取输入法中的文本，可以使用此事件）
        editableDiv.addEventListener('compositionupdate', function(e) {
            // e.data 包含当前输入法组合中的文本
            console.log('输入更新  	:', e.data);
            clearSelection();
        });


        editableDiv.addEventListener('compositionend', function(e) {
            console.log(e.data); // 打印输入的文字
            // 更新canvas的文本内容
            isComposing = false;
                // // e.data 包含完成输入的复合字符
                // // 在这里插入字符到文本中
                const beforeCursor = textData.text.slice(0, cursorPosition);
                const afterCursor = textData.text.slice(cursorPosition);
                textData.text = beforeCursor + e.data + afterCursor;
                updateStylesForInsert(cursorPosition,e.data.length);
                cursorPosition += e.data.length; // 更新光标位置，这里假设插入文本的长度就是e.data的长度
                console.log('C1 输入完成	:', e.data," 字符串长",e.data.length);

               
                electionStart = null;
                selectionEnd = null;
            
                cursorVisible = true; // 可选：同时隐藏光标             

                renderText(); // 重新渲染文本和样式
        });

        // 确保editableDiv可以接收输入
       
        editableDiv.addEventListener('paste', function(e) {
            e.preventDefault(); // 阻止默认粘贴行为

            // 获取剪贴板中的文本内容
            const pasteText = e.clipboardData.getData('text');

            // 假定cursorPosition是当前光标在textData.text中的位置
            if (cursorPosition === null) {
                cursorPosition = textData.text.length; // 如果没有指定光标位置，则默认为文本末尾
            }

            // 将文本插入到光标位置
            const beforeCursor = textData.text.slice(0, cursorPosition);
            const afterCursor = textData.text.slice(cursorPosition);
            textData.text = beforeCursor + pasteText + afterCursor;

          

            // 更新样式（这个示例没有改变样式，但你可能需要根据插入的文本更新样式）
            updateStylesForInsert(cursorPosition, pasteText.length);

              // 更新光标位置
              cursorPosition += pasteText.length;

            // 重新渲染文本
            renderText();
        });

        function copyToClipboard() {
                if (selectionStart === null || selectionEnd === null || selectionStart === selectionEnd) {
                    console.log("没有选中的文本可以复制");
                    return;
                }

                // 提取选中的文本
                const selectedText = textData.text.slice(selectionStart, selectionEnd);

                // 创建一个临时的textarea元素用于复制操作
                const textarea = document.createElement('textarea');
                textarea.value = selectedText;
                document.body.appendChild(textarea);

                // 选中文本并复制
                textarea.select();
                document.execCommand('copy');

                // 移除临时创建的textarea
                document.body.removeChild(textarea);

                console.log("已复制到剪贴板: " + selectedText);
        }



   

            function insertTextAtPosition(text, position) {
                // 插入文本并更新textData.text
                const beforeText = textData.text.substring(0, position);
                const afterText = textData.text.substring(position);
                textData.text = beforeText + text + afterText;

                // 更新样式范围
                updateStylesForInsert(position, text.length);

                // 重新渲染文本（这取决于你的渲染逻辑）
                renderText();
            }

        // 调整原有的keydown事件监听器，只在非组合输入时处理
        document.addEventListener('keydown', function(e) {
            if (!isComposing && e.key.length === 1) {
                // 处理英文输入等非复合字符输入
            }
            // 其他按键处理，比如Backspace, Delete等
        });


        function isInSelection(x, y) {
    // 首先，确定y坐标所在的行
    let accumulatedHeight = 50; // 初始行高，根据实际情况调整
    let globalCharIndex = 0; // 全局字符索引

    for (let line of linesData) {
        if (y >= accumulatedHeight && y < accumulatedHeight + line.height) {
            // 现在，我们找到了y坐标所在的行，接下来确定x坐标是否在选区内
            if (globalCharIndex + line.text.length < selectionStart || globalCharIndex > selectionEnd) {
                // 如果整行都在选区之外
                return false;
            }

            // 计算行内的选区开始和结束位置
            const lineSelectionStart = Math.max(selectionStart - globalCharIndex, 0);
            const lineSelectionEnd = Math.min(selectionEnd - globalCharIndex, line.text.length);

            // 累加当前行字符的宽度，以确定选区的x坐标范围
            let cursorX = 0;
            for (let i = 0; i < line.text.length; i++) {
                if (i >= lineSelectionStart && i < lineSelectionEnd) {
                    // 如果在选区范围内，检查x坐标是否落在当前字符的宽度范围内
                    const char = line.text[i];
                    const charStyle = line.styles.find(s => i >= s.start && i < s.end) || { fontSize: 16, font: "Arial", color: "black" };
                    ctx.font = `${charStyle.fontSize}px ${charStyle.font}`;
                    const metrics = ctx.measureText(char);
                    const charWidth = metrics.width;

                    if (x >= cursorX && x < cursorX + charWidth) {
                        // 如果x坐标在当前字符的宽度范围内，则说明光标在选区内
                        return true;
                    }
                }
                // 更新cursorX以反映当前行内的位置
                cursorX += ctx.measureText(line.text[i]).width;
            }

            // 如果循环结束还没有找到匹配，说明点击位置不在选区内
            return false;
        }
        accumulatedHeight += line.height; // 更新累加的行高
        globalCharIndex += line.text.length; // 更新全局字符索引
    }

    // 如果循环结束还没有找到匹配的行，说明点击位置不在任何选区内
    return false;
}


  		let currentLineHeight = 40;
        function generateLinesData() {
            linesData = []; // 重置行数据
            let currentLineText = '';
            let currentLineStyles = [];
            let cursorX = 0;
            let currentLineMaxHeight = 0; // 当前行的最大字体高度
            let currentLineStartIndex = 0; // 记录当前行在原文中的起始位置

            textData.text.split('').forEach((char, index) => {
                // 查找字符对应的样式，没有则使用默认样式
                const charStyle = textData.styles.find(s => index >= s.start && index < s.end) || { fontSize: 16, font: "Arial", color: "black" };
                ctx.font = `${charStyle.fontSize}px ${charStyle.font}`;
                const metrics = ctx.measureText(char);
                const charWidth = metrics.width;

                // 更新当前行中的最大字体高度
                currentLineMaxHeight = Math.max(currentLineMaxHeight, charStyle.fontSize); // 假设行高是字体大小的1.2倍

                if (cursorX + charWidth > canvas.width && currentLineText) {
                    // 结束当前行，并处理行的样式
                    linesData.push({
                        text: currentLineText,
                        styles: JSON.parse(JSON.stringify(currentLineStyles)), // 深拷贝样式以避免引用问题
                        y: 0, // 初始y坐标，稍后计算
                        height: currentLineMaxHeight
                    });

                    // 重置当前行信息，开始新的一行
                    currentLineText = '';
                    currentLineStyles = [];
                    cursorX = 0;
                    currentLineMaxHeight = charStyle.fontSize; // 重置最大高度为新行的第一个字符的高度
                    currentLineStartIndex = index;
                }

                // 更新当前行文本和样式
                currentLineText += char;
                updateCurrentLineStyles(currentLineStyles, charStyle, index - currentLineStartIndex);

                cursorX += charWidth;
            });

            // 添加最后一行，如果存在
            if (currentLineText) {
                linesData.push({
                    text: currentLineText,
                    styles: JSON.parse(JSON.stringify(currentLineStyles)),
                    y: 0, // 初始y坐标，稍后计算
                    height: currentLineMaxHeight
                });
            }

            // 现在更新每行的y坐标
            updateLineYCoordinates();
        }

        function updateLineYCoordinates() {
            let currentY = 50; // 假设文档的起始Y坐标为0
            let lineSpacing = 15; // 行间距，根据需要进行调整

            linesData.forEach((line, index) => {
                if (index === 0) {
                    line.y = currentY; // 第一行的Y坐标直接使用currentY
                } else {
                    currentY += lineSpacing; // 从第二行开始，在前一行的高度基础上加上行间距
                    line.y = currentY;
                }
                currentY += line.height; // 更新currentY为下一行的起始Y坐标
            });
        }
        
        function updateCurrentLineStyles(currentLineStyles, charStyle, relativeIndex) {
            let styleExists = false;
            currentLineStyles.forEach(style => {
                if (style.font === charStyle.font && style.fontSize === charStyle.fontSize && style.color === charStyle.color) {
                    style.end = relativeIndex + 1; // 更新样式的结束位置
                    styleExists = true;
                }
            });
            if (!styleExists) {
                currentLineStyles.push({ ...charStyle, start: relativeIndex, end: relativeIndex + 1 });
            }
        }







		var hadGen=0;
        function renderText() {
            if (hadGen == 0) {
                generateLinesData();
                hadGen = 1; // 确保generateLinesData只被调用一次，除非有更新
            }

            ctx.clearRect(0, 0, canvas.width, canvas.height); // 清除画布，准备新的渲染
            highlightSelection(); // 高亮选中区域，如果有的话

            // 遍历每一行文本来渲染
            const rect = canvas.getBoundingClientRect();
            
            const y1 = rect.top;
            
            let offsetY=50;
            linesData.forEach(line => {
                let cursorX = 0;
                let lineMaxAscent = 0;
                let lineMaxDescent = 0;

                // 首先，遍历一次以找出最大的ascent和descent
                line.styles.forEach(style => {
                    ctx.font = `${style.fontSize}px ${style.font}`;
                    const metrics = ctx.measureText("M"); // 使用"M"是因为它通常具有最大的ascent
                    lineMaxAscent = Math.max(lineMaxAscent, metrics.actualBoundingBoxAscent);
                    lineMaxDescent = Math.max(lineMaxDescent, metrics.actualBoundingBoxDescent);
                });

                // offsetY 应该基于最大的 ascent 调整，以确保文字底对齐
                let baseLineY = offsetY + lineMaxAscent;

                line.styles.forEach(style => {
                    ctx.font = `${style.fontSize}px ${style.font}`;
                    ctx.fillStyle = style.color;

                    const textSnippet = line.text.substring(style.start, style.end);
                    // 使用baseLineY作为基线绘制文本
                    ctx.fillText(textSnippet, cursorX, baseLineY);

                    cursorX += ctx.measureText(textSnippet).width;
                });

                // 更新offsetY为下一行的起始点，包括最大descent和预定的行间距
                offsetY += (lineMaxAscent + lineMaxDescent + parseInt(30)); // 假设30是行间距
            });

            // 光标绘制逻辑（如果有的话）
            if (cursorPosition !== null && cursorVisible) {
                drawCursor(cursorPosition); // 假设这个函数处理光标的绘制
            }
        }



        function highlightSelection(highlightColor) {
            highlightColor = 'rgba(173, 216, 230, 0.5)'; // 浅蓝色半透明
            let globalPos = 0; // 全局位置指针
           
            let lineSpacing=15;
            ctx.fillStyle = highlightColor; // 设置高亮颜色

            linesData.forEach(line => {
                const lineLength = line.text.length;
                const lineSelectionStart = Math.max(selectionStart - globalPos, 0);
                const lineSelectionEnd = Math.min(selectionEnd - globalPos, lineLength);

                if (lineSelectionStart < lineSelectionEnd) { // 有选中文本的行
                    const lineMetrics = calculateLineMetrics(ctx, line, lineSelectionStart, lineSelectionEnd);

                    // 直接使用line.y进行定位，加上偏移量
                    const startY = line.y  ;//- lineMetrics.maxHeight / 2; // 垂直居中调整

                    // 绘制高亮区域，应用X方向偏移和调整后的Y方向偏移
                    ctx.fillRect(lineMetrics.startX , startY, lineMetrics.totalWidth, lineMetrics.maxHeight);
                }

                globalPos += lineLength;
            });
        }

        function calculateLineMetrics(ctx, line, lineSelectionStart, lineSelectionEnd) {
            let maxHeight = 0; // 选区内最大字体高度
            let startX = Number.MAX_VALUE; // 选区开始的X坐标
            let totalWidth = 0; // 累计宽度，用于计算宽度
            let accumulatedWidth = 0; // 累计宽度，用于计算startX

            line.styles.forEach(style => {
                ctx.font = `${style.fontSize}px ${style.font}`; // 更新字体样式以进行测量
                const textPart = line.text.substring(Math.max(style.start, lineSelectionStart), Math.min(style.end, lineSelectionEnd));

                if (textPart.length > 0) {
                    const metrics = ctx.measureText(textPart);
                    const textWidth = metrics.width;
                    const textHeight = metrics.actualBoundingBoxAscent + metrics.actualBoundingBoxDescent;

                    maxHeight = Math.max(maxHeight, textHeight); // 更新最大高度

                    // 确定选区开始的X坐标
                    if (style.start < lineSelectionStart) {
                        accumulatedWidth += ctx.measureText(line.text.substring(style.start, lineSelectionStart)).width;
                    } else if (startX === Number.MAX_VALUE) {
                        startX = accumulatedWidth; // 首次遇到重叠部分设置起始X坐标
                    }

                    totalWidth += textWidth; // 累加宽度
                    accumulatedWidth += textWidth;
                } else {
                    // 对于未选中的部分，累加宽度以确定后续选中部分的位置
                    accumulatedWidth += ctx.measureText(line.text.substring(style.start, style.end)).width;
                }
            });

            if (startX === Number.MAX_VALUE) {
                startX = 0; // 如果没有选中的文本，重置起始X坐标
            }

            return { startX, maxHeight, totalWidth };
        }

        function calculateTextWidth(text, fontSize, font) {
            // 实现具体的文本宽度计算逻辑
            // 可以使用canvas的measureText方法，这里需要传入ctx和正确设置font
            ctx.font = `${fontSize}px ${font}`;
            return ctx.measureText(text).width;
        }


        function drawCursor(position) {
            if (position === null || position < 0) return;

            let accumulatedHeight = 0; // 用于计算光标的Y坐标
            let cursorX = 0; // 光标的X坐标
            let cursorY = 0; // 光标的起始Y坐标
            let accumulatedLength = 0; // 累计字符长度，帮助定位光标所在行
            let lineFound = false; // 标记是否找到了光标所在行

            for (let line of linesData) {
                const lineHeight = line.height;
                if (position < accumulatedLength + line.text.length + 1 && !lineFound) {
                    // 光标位于当前行
                    cursorY = accumulatedHeight;
                    let linePosition = position - accumulatedLength; // 光标在当前行的位置
                    cursorX = linePosition === 0 ? 0 : getCursorXInLine(line, linePosition - 1);
                    lineFound = true; // 标记找到光标所在行，避免进一步循环
                }
                accumulatedLength += line.text.length;
                if (!lineFound) {
                    accumulatedHeight += lineHeight;
                }
            }

            // 绘制光标
            if (lineFound) {
                ctx.beginPath();
                ctx.moveTo(cursorX, cursorY);
                ctx.lineTo(cursorX, cursorY + lineHeight); // 使用当前行的高度作为光标高度
                ctx.strokeStyle = 'black';
                ctx.lineWidth = 1; // 可根据需要调整光标宽度
                ctx.stroke();
            }
        }


        function getCursorXInLine(line, position) {
            // 计算光标在行内的X坐标
            let cursorX = 0;
            for (let i = 0; i <= position; i++) {
                if (i < line.text.length) {
                    const char = line.text[i];
                    const charStyle = line.styles.find(s => i >= s.start && i < s.end) || { fontSize: 16, font: "Arial", color: "black" };
                    ctx.font = `${charStyle.fontSize}px ${charStyle.font}`;
                    const metrics = ctx.measureText(char);
                    if (i === position) return cursorX + metrics.width; // 如果到达光标位置，返回当前累计宽度
                    cursorX += metrics.width;
                }
            }
            return cursorX;
        }

        function clearSelection() {
                // 重置选择状态
                selectionStart = null;
                selectionEnd = null;

    			//isSelecting = false;
                // 如果需要在取消选择时也隐藏光标，取消以下注释
                // cursorPosition = null;
                // cursorVisible = false; // 可选：同时隐藏光标

                // 重新渲染画布以更新显示状态
                renderText();
            }

        
        function blinkCursor() {
                cursorVisible = !cursorVisible;
                renderText();
            }

            setInterval(blinkCursor, 500); // 设置光标闪烁的间隔

            // 事件处理函数与之前相同，但需要更新以管理光标位置和可见性


        function getCharIndex(x) {
            for (let i = 0; i < charWidths.length; i++) {
                const { width, x: charX } = charWidths[i];
                if (x >= charX && x <= charX + width) {
                    return i;
                }
            }
            return null;
        }
        //用坐标返回当前字符内容
        function getCharByIndex(position) {
            // 累计前面所有行的字符长度，以定位到正确的字符
            console.log("getCharByIndx:",position);
            let accumulatedLength = 0;
            for (let line of linesData) {
                if (position < accumulatedLength + line.text.length) {
                    return line.text[position - accumulatedLength];
                }
                accumulatedLength += line.text.length;
            }
            return ''; // 如果索引超出范围，返回空字符串
        }


        function updateCursorPosition(x) {
            const rect = canvas.getBoundingClientRect();
            const clickX = x - rect.left; // 获取相对于canvas的点击位置
            cursorPosition = getCharIndex(clickX);
            cursorVisible = true; // 显示光标
        }

        function updateStylesForInsert(insertPosition, insertLength) {
            var isFind=false;
            console.log("C3 -> 更新样式！：",insertPosition);
            const selectedText = textData.text.substring(selectionStart, selectionEnd);
                    console.log("C3 选中文字",selectedText); // Print the selected text to the console

            var tempIndex=0;
                textData.styles.forEach(style => {

                    console.log("C1 -> forEach 更新样式！：",insertPosition," tempIndex:" ,tempIndex);
                    tempIndex++;
                    if(isFind==false)
                    {
                        // 如果插入位置在当前样式范围之前，或恰好在其开始位置
                        if (insertPosition==0) 
                        {
                           
                        	style.start =0;
                        	style.end += insertLength;
                            isFind=true;
                            console.log("C1 ->",insertPosition," 在当前样式之前  style.start="+insertPosition);
                            //当字符输入完成之后insertPosition会自增 ，要注意这个现象 。。
                        }
                        else
                        {  //插入位置在当前样式范围之间结束位置+1
                            if (insertPosition> style.start&&insertPosition <style.end)  
                            {
                                console.log("C1 -> ",insertPosition," 在当前样式之间");
                                style.end += insertLength;
                                isFind=true;
                            }else
                            {
                                if(insertPosition===style.end)
                                {
                                    console.log("C1 ->",insertPosition," 在当前样式最后尾");
                                    style.end += insertLength;
                                    isFind=true;
                                }

                            }

                           
                        }
                      
                        

                    }                    
                     else {
                        // 如果插入位置在当前样式范围内部，只需要调整结束位置
                        style.start += insertLength;
                        style.end += insertLength;
                    }
                    
                });
            }

        canvas.addEventListener('mousedown', function(e) {
            const rect = canvas.getBoundingClientRect();
            const x = e.clientX - rect.left;
            const y = e.clientY - rect.top;
            const charIndex = getCharIndex(x, y); //通过 x y 坐标获取字符索引是否对应有值 。。

            console.log("C4 点击鼠标下所在字符的序号：", charIndex);
            var selectChar = getCharByIndex(charIndex);
            console.log("C4 点击鼠标下所在字符是：", selectChar);
            //这里要返回这个字符前的坐，，用来显示光标
            console.log("C4 点击鼠标位置X:", x," y:",y);

            var  posX=getCharPosition(x,y);
            console.log("C4 内容坐标:", posX);
            if (isInSelection(x, y)) {
                // 点击在已选区域内，保持选择
                cursorVisible = true; // 可选：同时显示光标
            } else {
                clearSelection(); // 如果点击非文本区域或新位置，则取消之前的选择
                if (charIndex !== -1) {
                    // 开始新的选择
                    isSelecting = true;
                    cursorPosition = charIndex; // 更新光标位置
                    selectionStart = charIndex;
                    selectionEnd = charIndex + 1;
                }
            }

            renderText(); // 重新渲染文本以更新画布状态   

            });

            function getCharIndex(x, y) {
                let accumulatedHeight = 50; // 用于累加行高，以确定点击位置的y坐标
 				let  lineSpacing=15;
                // 遍历每一行文本
                for (let lineIndex = 0; lineIndex < linesData.length; lineIndex++) {
                    const line = linesData[lineIndex]; //取出一行数据，，同时计算生成Y 坐标用于比对
                   
                    //console.log("C3 lineStr: " + line.text);
                    if (y >= accumulatedHeight && y < accumulatedHeight + line.height) {
                        // 确定鼠标点击在当前行内
                        let cursorX = 0; // 累加当前行字符的宽度，以确定点击位置的x坐标
                        let accumulatedLength = linesData.slice(0, lineIndex).reduce((acc, cur) => acc + cur.text.length, 0); // 累加之前所有行的字符长度，以确定全局字符索引

                        for (let i = 0; i < line.text.length; i++) {
                            const char = line.text[i];
                            // 查找该字符的样式
                            //console.log("C3 按字符查样：： ",char);
                            const charStyle = line.styles.find(s => i >= s.start && i < s.end) || { fontSize: 16, font: "Arial", color: "black" };
                            ctx.font = `${charStyle.fontSize}px ${charStyle.font}`;
                            const metrics = ctx.measureText(char);
                            const charWidth = metrics.width;

                            if (x >= cursorX && x < cursorX + charWidth) {
                                // 如果点击位置在当前字符的宽度范围内，则返回这个字符的全局索引
                                return accumulatedLength + i;
                            }

                            cursorX += charWidth; // 更新累加的宽度，以反映当前行内的位置
                        }
                        // 如果x坐标超出了当前行的最后一个字符，但y坐标仍在当前行内，则视为点击了该行的最后一个字符
                        return accumulatedLength + line.text.length - 1;
                    }
                    accumulatedHeight += line.height+lineSpacing; // 更新累加的行高，以反映不同行的位置
                }

                return -1; // 如果没有找到对应的字符，返回-1
            }

            //重原来返回下标索引 改为X,Y坐标

            function getCharPosition(x, y) {
                let accumulatedHeight = 50; // 用于累加行高，以确定点击位置的y坐标

                for (let lineIndex = 0; lineIndex < linesData.length; lineIndex++) {
                    const line = linesData[lineIndex];
                    //console.log("C3 lineStr: " + line.text);
                    if (y >= accumulatedHeight && y < accumulatedHeight + line.height) {
                        // 确定鼠标点击在当前行内
                        let cursorX = 0; // 累加当前行字符的宽度，以确定点击位置的x坐标
                        let accumulatedLength = linesData.slice(0, lineIndex).reduce((acc, cur) => acc + cur.text.length, 0); // 累加之前所有行的字符长度，以确定全局字符索引

                        for (let i = 0; i < line.text.length; i++) {
                            const char = line.text[i];
                            // 查找该字符的样式
                           // console.log("C3 按字符查样：： ",char);
                            const charStyle = line.styles.find(s => i >= s.start && i < s.end) || { fontSize: 16, font: "Arial", color: "black" };
                            ctx.font = `${charStyle.fontSize}px ${charStyle.font}`;
                            const metrics = ctx.measureText(char);
                            const charWidth = metrics.width;

                            if (x >= cursorX && x < cursorX + charWidth) {
                                // 如果点击位置在当前字符的宽度范围内，则返回这个字符的全局索引
                                return cursorX;
                            }

                            cursorX += charWidth; // 更新累加的宽度，以反映当前行内的位置
                        }
                        // 如果x坐标超出了当前行的最后一个字符，但y坐标仍在当前行内，则视为点击了该行的最后一个字符
                        return cursorX;
                    }
                    accumulatedHeight += line.height; // 更新累加的行高，以反映不同行的位置
                }

                return -1; // 如果没有找到对应的字符，返回-1
            }




            let lastSelectionEnd = null; // 全局变量，记录上次选择结束的位置

            canvas.addEventListener('mousemove', function(e) {
            if (isSelecting) {
                const rect = canvas.getBoundingClientRect();
                const x = e.clientX - rect.left;
                const y = e.clientY - rect.top;
                const charIndex = getCharIndex(x, y);

                if (charIndex !== -1) {
                    if (selectionStart === null) {
                        // 初始化选择起点
                        selectionStart = charIndex;
                    }
                    // 更新选择结束位置
                    selectionEnd = charIndex + 1;

                    // 为了增强体验，使得选择区域能够逐字覆盖，我们需要确保光标位置精确
                    // 这可能需要调整`getCharIndex`函数，使其更加精确地返回字符索引

                    // 如果当前的选择终点与上一次不同，则需要重绘
                    if (lastSelectionEnd !== selectionEnd) {
                        lastSelectionEnd = selectionEnd; // 更新记录的选择终点

                        console.log("C4 lastSelectionEnd", lastSelectionEnd);
                        renderText(); // 重新渲染文本以更新选择区域的高亮显示
                    }
                }
            }
        });




        canvas.addEventListener('mouseup', function(e) {
            const rect = canvas.getBoundingClientRect();
            const x = e.clientX - rect.left;
            const y = e.clientY - rect.top;

            if (isSelecting) {
                // 更新选择结束位置
                const charIndex = getCharIndex(x, y);

                if (charIndex >= 0) { // 确保得到了有效的字符索引
                    if (selectionStart !== null) {
                        selectionEnd = charIndex + 1;
                    } else {
                        selectionStart = charIndex;
                        selectionEnd = charIndex + 1;
                    }
                }

                if (selectionStart !== null && selectionEnd !== null && selectionStart !== selectionEnd) {
                    // 完成了有效选择
                    cursorVisible = false; // 根据需要隐藏光标
                } else {
                    // 未进行有效选择，可能仅是单击了位置
                    if (charIndex >= 0) {
                        updateCursorPosition(x); // 可能需要更新光标位置函数以适应多行
                    }
                    clearSelection(); // 清除选择
                }
            }

            isSelecting = false; // 重置选择状态
            renderText(); // 重新渲染以应用最终状态
        });


        canvas.addEventListener('dblclick', function(e) {
            const rect = canvas.getBoundingClientRect();
            const x = e.clientX - rect.left;
            const y = e.clientY - rect.top;

            // 确认点击位置是否在已有的选区内
            if (isInSelection(x, y)) {
                // 如果是在选区内双击，我们需要累计选区内的文本
                let selectedText = "";
                let accumulatedLength = 0; // 累计长度以确定字符的全局索引位置

                for (let line of linesData) {
                    // 对于每一行，检查其是否部分或完全在选区内
                    if (accumulatedLength + line.text.length >= selectionStart && accumulatedLength <= selectionEnd) {
                        // 计算行内选区的开始和结束位置
                        const lineSelectionStart = Math.max(selectionStart - accumulatedLength, 0);
                        const lineSelectionEnd = Math.min(selectionEnd - accumulatedLength, line.text.length);
                        // 累计选区内的文本
                        selectedText += line.text.substring(lineSelectionStart, lineSelectionEnd);
                    }
                    accumulatedLength += line.text.length;
                    // 如果累计长度超过了选区的结束，就不需要继续检查后续的行了
                    if (accumulatedLength > selectionEnd) {
                        break;
                    }
                }

                console.log("C4 我双击了 ：：",selectedText); // 打印选中的文本到控制台
            }
        });


            function updateStylesForDelete(position, length) {
                console.log("updateStylesForDelete");
                textData.styles.forEach(style => {
                    if (position < style.start) {
                        style.start -= length;
                        style.end -= length;
                    } else if (position < style.end) {
                        style.end -= length;
                    }

                    // 防止start和end反转
                    if (style.start > style.end) {
                        style.start = style.end;
                    }
                });
            }


            document.addEventListener('keydown', function(e) {
                
                
                  // 检测是否同时按下了Ctrl键和C键
                  if (e.ctrlKey && e.key === 'c') 
                {
                    // 调用复制到剪贴板的函数
                    copyToClipboard();

                    e.preventDefault(); // 防止默认的复制行为
                }
             

                // 确保不是控制键，比如箭头键、Backspace等
                if (e.key.length === 1) {
                    // 在这里插入字符到textData.text
                    const beforeCursor = textData.text.slice(0, cursorPosition);
                    const afterCursor = textData.text.slice(cursorPosition);
                    console.log("英文输入 前：：",textData.text);

                    
                
                    textData.text = beforeCursor + e.key + afterCursor;
                    //在这里要调整输入字母的样式 ，增加了一个

                    console.log("英文输入后 ：：",textData.text," 插入位置  ",cursorPosition);
                    
                    updateStylesForInsert(cursorPosition,1);
                    electionStart = null;
                    selectionEnd = null;
                    // 如果需要在取消选择时也隐藏光标，取消以下注释
                    // cursorPosition = null;
                       cursorVisible = true; // 可选：同时隐藏光标
                        // 更新光标位置
                    cursorPosition++;

                    renderText(); // 重新渲染文本
                }

                if (e.key === 'Backspace' && cursorPosition > 0) {
                    const beforeCursor = textData.text.slice(0, cursorPosition - 1);
                    const afterCursor = textData.text.slice(cursorPosition);
                    textData.text = beforeCursor + afterCursor;
                    electionStart = null;
                    selectionEnd = null;

                    cursorPosition--;

                    renderText();
                } 
                else if (e.key === 'Delete' && cursorPosition < textData.text.length) {
                    const beforeCursor = textData.text.slice(0, cursorPosition);
                    const afterCursor = textData.text.slice(cursorPosition + 1);
                    textData.text = beforeCursor + afterCursor;

                    // 光标位置不变
                    electionStart = null;
                    selectionEnd = null;

                    renderText();
                }

                if (e.key === 'ArrowLeft' && cursorPosition > 0) {
                    cursorPosition--;
                    console.log("《《光标位置移动",cursorPosition);
                    electionStart = null;
                    selectionEnd = null;
                    
                    renderText();
                } 
                else if (e.key === 'ArrowRight' && cursorPosition <= textData.text.length) {
                    cursorPosition++;
                    console.log("光标位置移动=》》",cursorPosition);
                    var chartCount=0;

                    charWidths.forEach(objectOfChar => {
                        console.log('width: '+objectOfChar.width + ' x:' + objectOfChar.x);
                      
                        chartCount++;
                        

                    });

                    if(cursorPosition>chartCount)
                    cursorPosition=chartCount;
                    electionStart = null;
                    selectionEnd = null;

                    renderText();
                }

               

        renderText();
    });


});

    </script>
</body>
</html>
